package utils.test.net;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.regex.Pattern;

import lion.dev.net.IP;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * IP库，通过读取纯真IP数据库文件来获得IP地址信息
 *
 * @author lion
 * @mail hl_0074@sina.com
 * @date 2011-7-20
 */
public class IPBase2 {

	public static String QQWRY_DAT = "qqwry.dat";
	private static final Pattern IPPattern = Pattern.compile("((25[0-5]|2[0-4]\\d|1?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|1?\\d\\d?)");

	public static final int IP_RECORD_LENGTH = 7;
	public static final byte AREA_FOLLOWED = 1;
	public static final byte AREA_NOT_FOLLOWED = 2;

	private static int START_OFFSET = 0;
	private static int END_OFFSET = 0;
	private static int IP_NUM = 0;
	private static byte[][] IP_INDEX = {};

	private byte[] buf4 = new byte[4];
	private byte[] buf3 = new byte[3];
	byte b = 0, i = 0;

	private static Log log = LogFactory.getLog(IPBase2.class);
	private RandomAccessFile raf;
	private static boolean inited = false;

	private IPBase2() {

		try {
			init();
			raf = new RandomAccessFile(IPBase2.class.getClassLoader().getResource(QQWRY_DAT).getFile(), "r");
		} catch (IOException e) {
			log.error("ip data file not found at classpath:" + IPBase2.QQWRY_DAT);
		}
	}

	public static IP query(String ip) {

		return new IPBase2().queryIP(ip);
	}

	private static void init() throws IOException {

		if (inited) { return; }
		inited = true;

		RandomAccessFile raf = null;

		try {
			raf = new RandomAccessFile(IPBase2.class.getClassLoader().getResource(QQWRY_DAT).getFile(), "r");
			byte[] buf = new byte[4];

			raf.read(buf);
			START_OFFSET = toInt4(buf);

			buf = new byte[4];
			raf.read(buf);

			END_OFFSET = toInt4(buf);
			IP_NUM = (END_OFFSET - START_OFFSET) / 7 + 1;

			IP_INDEX = new byte[IP_NUM][7];
			raf.seek(START_OFFSET);
			for (int i = 0; i < IP_NUM; i++) {
				raf.readFully(IP_INDEX[i]);
			}

			log.debug("ipbase init finish");
			log.debug("ip num:" + IP_NUM);
			log.debug("ip start offset:" + START_OFFSET);
			log.debug("ip end offset:" + END_OFFSET);
		} catch (Exception e) {
			log.error("init failed,maybe ip data file not exists or have been broken");
		} finally {
			if (raf != null) {
				raf.close();
			}
		}
	}

	public IP queryIP(String ipStr) {

		IP ip = new IP();
		if (raf == null || !isIPStr(ipStr)) { return ip; }

		int ipidx = locateIP(ip2ByteArray(ipStr));
		if (ipidx == -1) { return ip; }

		ip.setStartIP(byte2IP(IP_INDEX[ipidx]));
		buf3 = Arrays.copyOfRange(IP_INDEX[ipidx], 4, 7);
		long endIPOffset = toLong3(buf3);

		try {
			raf.seek(endIPOffset);
			raf.read(buf4);
			ip.setEndIP(byte2IP(buf4));

			b = raf.readByte();
			if (b == AREA_FOLLOWED) { // 国家有偏移，记录跟着国家偏移后面
				readMod1(ip);
			} else if (b == AREA_NOT_FOLLOWED) { // 国家有偏移，地区不跟着国家走
				readMod2(ip, endIPOffset);
			} else { // 国家没有偏移,后面直接跟着
				raf.seek(raf.getFilePointer() - 1);
				readNormal(ip);
			}
		} catch (Exception e) {
		} finally {
			try {
				raf.close();
			} catch (IOException e) {
			}
		}
		return ip;
	}

	private boolean isIPStr(String ipStr) {

		return IPPattern.matcher(ipStr).matches();
	}

	private void readNormal(IP ip) throws IOException, UnsupportedEncodingException {

		ip.setArea(readString(raf.getFilePointer()));
		ip.setPosition(readString(raf.getFilePointer()));
	}

	private void readMod1(IP ip) throws IOException, UnsupportedEncodingException {

		long countryOffset = readLong3();
		raf.seek(countryOffset);
		byte b = raf.readByte();
		long areaOffset = 0;
		if (b == 1 || b == 2) {
			areaOffset = countryOffset + 4;
			ip.setArea(readString(countryOffset));
			ip.setPosition(readString(areaOffset));
		} else {
			ip.setArea(readString(countryOffset));
			ip.setPosition(readString(raf.getFilePointer()));
		}
	}

	private void readMod2(IP ip, long endIPOffset) throws IOException, UnsupportedEncodingException {

		long countryOffset = readLong3();
		ip.setArea(readString(countryOffset));

		long areaOffset = endIPOffset + 8;
		ip.setPosition(readString(areaOffset));
	}

	private String readString(long offset) throws IOException {

		if (raf.getFilePointer() != offset) {
			raf.seek(offset);
		}

		byte b = raf.readByte();
		if (b == 1 || b == 2) {
			offset = readLong3();
			return readString(offset);
		}
		raf.seek(offset);

		byte i = 0;
		byte[] sbuf = new byte[255];
		while ((b = raf.readByte()) != 0) {
			sbuf[i++] = b;
		}
		return new String(sbuf, 0, i, "GBK");
	}

	private int locateIP(byte[] ip) {

		for (int begin = 0, end = IP_INDEX.length; begin < end;) {
			int mid = (end + begin) / 2;
			if (mid <= 0) {
				mid = 1;
			}
			int r = compareIP(ip, IP_INDEX[mid]);
			if (end == begin + 1) {
				return begin;
			} else if (r > 0) {
				begin = mid;
			} else if (r < 0) {
				end = mid;
			} else {
				return mid;
			}
		}
		return -1;
	}

	private String byte2IP(byte[] ip) {

		StringBuilder sbu = new StringBuilder();
		int i = 3;
		while (i > 0) {
			sbu.append(ip[i--] & 0xff).append(".");
		}
		sbu.append(ip[i] & 0xff);

		return sbu.toString();
	}

	private int compareIP(byte[] ip, byte[] ip2) {

		for (int i = 3; i > -1; i--) {
			int a = ip[i] & 0xff;
			int b = ip2[i] & 0xff;
			if (a > b) {
				return 1;
			} else if (a < b) { return -1; }
		}
		return 0;
	}

	public byte[] ip2ByteArray(String ip) {

		byte[] ret = new byte[4];
		String[] ips = ip.split("\\.");
		for (int i = 3, j = 0; i > -1; i--, j++) {
			ret[i] = (byte) (Integer.parseInt(ips[j]) & 0xff);
		}
		return ret;
	}

	private static int toInt4(byte[] buf) {

		int ret = buf[0] & 0xff;
		ret |= (buf[1] << 8) & 0xff00;
		ret |= (buf[2] << 16) & 0xff0000;
		ret |= (buf[3] << 24) & 0xff000000;

		return ret;
	}

	private long toLong3(byte[] b3) {

		long ret = b3[0] & 0xff;
		ret |= (b3[1] << 8) & 0xff00;
		ret |= (b3[2] << 16) & 0xff0000;

		return ret;
	}

	private long readLong3() throws IOException {

		raf.read(buf3);
		long countryOffset = toLong3(buf3);
		return countryOffset;
	}
}
